> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/jaypopat/cf_ai_duet/llms.txt
> Use this file to discover all available pages before exploring further.

# Durable Objects

> Per-room state management with DuetAgent

## Overview

Duet uses Cloudflare Durable Objects to maintain isolated, persistent state for each collaboration room. Each room gets its own `DuetAgent` instance that stores conversation history and coordinates with the room's dedicated sandbox.

## DuetAgent State

Each DuetAgent maintains a simple state structure:

```typescript theme={null}
interface DuetAgentState {
  messages: DuetMessage[];
}

interface DuetMessage {
  role: "user" | "agent";
  userId?: string;
  text: string;
  ts: number;
}

export class DuetAgent extends Agent<Env, DuetAgentState> {
  override initialState: DuetAgentState = { messages: [] };
}
```

**Source:** `~/workspace/source/cf-worker/index.ts:18-90`

## State Isolation

Each room is identified by a unique `roomId`. The worker extracts this from the URL and routes to the appropriate DuetAgent instance:

```typescript theme={null}
const agent = await getAgentByName(env.DUET_AGENT, roomId);
```

**Source:** `~/workspace/source/cf-worker/index.ts:67`

This ensures:

* Each room has isolated conversation history
* Multiple rooms can operate simultaneously without interference
* State persists across requests for the same room

## Message History Management

The DuetAgent maintains conversation history with automatic limits:

### Recent Context for AI

When generating responses, the agent uses the last 10 messages as context:

```typescript theme={null}
const aiMessages: AIMessage[] = [
  {
    role: "system",
    content: "You are Duet, a concise pair-programming assistant..."
  },
  ...this.state.messages.slice(-10).map<AIMessage>((m) => ({
    role: m.role === "agent" ? "assistant" : "user",
    content: m.text,
  })),
  { role: "user", content: userMsg.text },
];
```

**Source:** `~/workspace/source/cf-worker/index.ts:154-168`

### Total History Limit

The agent stores up to 50 messages in total to prevent unbounded growth:

```typescript theme={null}
const nextMessages = [...this.state.messages, userMsg, agentMsg].slice(-50);
this.setState({ messages: nextMessages });
```

**Source:** `~/workspace/source/cf-worker/index.ts:179-180`

## State Updates

Every message interaction updates the state:

1. User message is added to history
2. AI generates response
3. Agent message is added to history
4. State is updated with both messages

```typescript theme={null}
const userMsg: DuetMessage = {
  role: "user",
  userId: data.userId?.trim(),
  text: data.text.trim(),
  ts: Date.now(),
};

const agentMsg: DuetMessage = {
  role: "agent",
  text: textWithOutputs,
  ts: Date.now(),
};

const nextMessages = [...this.state.messages, userMsg, agentMsg].slice(-50);
this.setState({ messages: nextMessages });

return Response.json({ reply: agentMsg.text, messages: nextMessages });
```

**Source:** `~/workspace/source/cf-worker/index.ts:147-182`

## Room Cleanup

When a room is deleted, the DuetAgent resets its state and destroys the associated sandbox:

```typescript theme={null}
private async handleCleanup(roomId: string): Promise<Response> {
  const errors: string[] = [];

  // Reset agent state
  this.setState({ messages: [] });

  // Terminate sandbox
  try {
    const sandbox = getSandbox(this.env.Sandbox, `sandbox-${roomId}`);
    await sandbox.destroy();
  } catch (e) {
    errors.push(`sandbox: ${e instanceof Error ? e.message : String(e)}`);
  }

  if (errors.length > 0) {
    return Response.json({ cleaned: true, errors }, { status: 207 });
  }

  return Response.json({ cleaned: true, roomId });
}
```

**Source:** `~/workspace/source/cf-worker/index.ts:244-266`

The cleanup is triggered via DELETE request from the client:

```go theme={null}
func (c *Client) CleanupRoom(ctx context.Context, roomID string) error {
  url := fmt.Sprintf("%s/api/rooms/%s", c.baseURL, roomID)
  req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
  // ...
}
```

**Source:** `~/workspace/source/internal/ai/client.go:106-125`

## Durable Objects Configuration

The worker configuration defines two Durable Object bindings:

```toml theme={null}
[durable_objects]
bindings = [
  { name = "DUET_AGENT", class_name = "DuetAgent" },
  { name = "Sandbox", class_name = "Sandbox" },
]

[[migrations]]
tag = "v1"
new_sqlite_classes = ["DuetAgent"]

[[migrations]]
tag = "v2"
new_sqlite_classes = ["Sandbox"]
```

**Source:** `~/workspace/source/cf-worker/wrangler.toml:6-21`

Migrations define the SQLite-backed storage for each Durable Object class.

## Next Steps

* [Cloudflare Workers](/architecture/cloudflare-workers) - Learn about request routing
* [LLM Integration](/architecture/llm-integration) - See how AI responses are generated
* [Sandboxes](/architecture/sandboxes) - Understand room-specific sandbox instances
